﻿using System;
using System.Collections.Generic;
using System.Threading;
using System.Configuration;
/// <summary>
/// 用于在多线程访问sqlite时防止同步写导致锁文件
/// 
/// 使用方法：
/// using (SQLiteWriteLock sqliteLock = new SQLiteWriteLock(SQLite链接字符串))
/// {
///     //sqlite 写操作代码
/// }
/// 
/// 可以通过在配置文件appSettings节中添加设置 SQLiteWriteLockTimeout 的value值控制锁等待的超时时间，该值必须为正整数数字，单位为毫秒，
/// 默认的超时时间是1000ms
/// </summary>
public sealed class SQLiteWriteLock : IDisposable
{
    #region 静态字段和属性
    const short WAIT_TIME = 5;
    static readonly object locker = new object();
    static Dictionary<string, int> _dbThreadIdDict = new Dictionary<string, int>();

    /// <summary>
    /// 获得写操作的超时时间，单位为毫秒，可以通过配置文件appSettings节中添加设置 SQLiteWriteLockTimeout 的value值控制锁等待的超时时间，该值必须为正整数数字，单位为毫秒
    /// 默认的超时时间是1000ms
    /// </summary>
    public static int SQLiteWriteLockTimeout
    {
        get
        {
            string configValule = ConfigurationManager.AppSettings["SQLiteWriteLockTimeout"];
            if (!string.IsNullOrEmpty(configValule))
            {
                return int.Parse(configValule);
            }
            return 1000;
        }
    }
    #endregion

    private readonly string _connString;

    //隐藏无参构造函数
    private SQLiteWriteLock() { }

    public SQLiteWriteLock(string connString)
    {
        _connString = connString;
        AcquireWriteLock();
    }

    #region 私有方法

    private void AcquireWriteLock()
    {
        int threadId = Thread.CurrentThread.ManagedThreadId;

        int waitTimes = 0;
        while (_dbThreadIdDict.ContainsKey(_connString) && _dbThreadIdDict[_connString] != threadId)
        {
            Thread.Sleep(WAIT_TIME);
            waitTimes += WAIT_TIME;
#if DEBUG
            Console.WriteLine(_connString + " wait for " + waitTimes + " ms");
#endif
            if (waitTimes > SQLiteWriteLockTimeout)
            {
                throw new TimeoutException("SQLite等待写操作超时");
            }
        }

        lock (locker)
        {
            if (!_dbThreadIdDict.ContainsKey(_connString))
                _dbThreadIdDict.Add(_connString, threadId);
        }
    }

    private void ReleaseWriteLock()
    {
        lock (locker)
        {
            if (_dbThreadIdDict.ContainsKey(_connString))
            {
                _dbThreadIdDict.Remove(_connString);
            }
        }
    }

    #endregion

    #region IDisposable 成员

    public void Dispose()
    {
        ReleaseWriteLock();
    }

    #endregion
}